/*
 * Copyright 2007 - The JDPF Project Team (http://www.jdpf.org) 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */
package org.jdpf.plugins.measureseries.transformer.textseries.quantitativetoqualitative;

import java.util.Date;
import java.util.List;

import javax.measure.Measure;
import javax.measure.unit.Unit;

import org.jdpf.core.kernel.elements.Token;
import org.jdpf.core.plugins.blocks.AbstractTransformerPlugin;
import org.jdpf.core.plugins.exceptions.PluginException;
import org.jdpf.core.plugins.exceptions.PluginExceptionsEnum;
import org.jdpf.plugins.datatypes.basic.MeasureAdapter;
import org.jdpf.plugins.datatypes.basic.MeasureSeries;
import org.jdpf.plugins.datatypes.basic.TextAdapter;
import org.jdpf.plugins.datatypes.basic.TextSeries;
import org.jdpf.plugins.datatypes.temporal.TimestampedMeasureAdapter;
import org.jdpf.plugins.datatypes.temporal.TimestampedTextAdapter;
import org.jscience.physics.amount.Amount;

/**
 * This plugin is a generator that reads integer values from a csv file, stores
 * them into a IntegerSeries data type. This series is finally inclosed to a
 * token and added to all output arc places.
 * 
 * @author Paolo Ciccarese
 * @author Cristiana Larizza
 * @version 1.0
 */

public class QuantitativeToQualitativeTransformer extends AbstractTransformerPlugin {
    
	/*
	 * Parameters list. We prefer to define the plugin parameters with an 
	 * underscore. For every plugin parameter it is necessary to implement 
	 * a 'set' method that is accepting a String variable. The framework,
	 * through reflection, will set the parameters at runtime. 
	 */
    private String _ranges; // Mandatory 
    private String _labels;	// Mandatory 
	
	private Unit _unitOfMeasure = Unit.ONE;	// Optional

	public void setRanges(String ranges) {
		_ranges = ranges;
	}
	
	public void setLabels(String labels) {
		_labels = labels;
	}
      
	public void setUnitOfMeasure(String unitOfMeasure) {
		_unitOfMeasure = Unit.valueOf(unitOfMeasure.subSequence(0, unitOfMeasure.length()));
	}

	/*
	 * Standard plugin constructor: every plugin has to implement the 
	 * constructor in this very same format.
	 */
    public QuantitativeToQualitativeTransformer(String transitionId, String transitionName) {
		super(transitionId, transitionName);
	}	
    
    @SuppressWarnings("unchecked")
	public Token process(List<Token> list) throws PluginException{
    	this.log("ranges: " + _ranges + " labels: " + _labels);
    	Criteria criteria = new Criteria(_ranges, _labels);
    	this.log(criteria.toString());
     	TextSeries outputSeries = new TextSeries();
    
     	try {
     		this.logMainPoint(this, this.getPluginId(), this.getId(), "Qualitative to qualitative transformation started.");
     		int processedBytes = 0;
     		TextAdapter value = null;
     		Date timestamp = null;

     		// It reads only the first list in the token.
     		List inList = list.get(0).getValueList();

     		// It reads only the first list in the token.
     		MeasureSeries ms = (MeasureSeries) inList.get(0);
     		TextSeries ts = new TextSeries();
     		this.log("processing " + ms.getSize() + " values");

     		int i=0;
     		while (start && i<ms.getSize()) { 
//     			this.log(this, this.getPluginId(), this.getId(), "processing value " + i+": "+((MeasureAdapter)ms.getElement(i)).getStringValue());
     			//this.log( ((MeasureAdapter)ms.getElement(i)).getStringValue());

     			// Updating progress percentage
     			//percentage = Math.min(100, ((double)processedBytes / ms.getSize()) * 100);

     			// TODO Checks tokens against line format
     			value = isInRange((MeasureAdapter)ms.getElement(i), criteria);
     			this.log(this, this.getPluginId(), this.getId(), "processing value " + i+": "+
     					((MeasureAdapter)ms.getElement(i)).getStringValue()+" label " + value.getStringValue());
//    			this.log(this.getPluginId(), this.getClass().getName(), "label " + value.getStringValue());
     			outputSeries.addElement(value);
     			i++;
     		}
     	} catch (Exception e) {
     		e.printStackTrace();
     		this.error(this, this.getPluginId(), this.getId(), "Illegal data format. "
     				+ e.getMessage());
     		throw new PluginException(PluginExceptionsEnum.ExecutionError, this.getPluginId(), "The input file " + " does not exist: "
     				+ e.getMessage());
     	}
     	this.logMainPoint(this, this.getPluginId(), "end of processing");
     	return setOutputToken(outputSeries);
    }

    public TextAdapter isInRange(MeasureAdapter val, Criteria criteria) {
    	this.log("number "+val.getValue());
    	for(int i=0; i<criteria.getRanges().length; i++){
        	this.log("i " + i);
  		        	
    		if( ((getAmount(val).isLargerThan(getAmount(criteria.getRanges()[i].getMin()))
    				|| (criteria.getRanges()[i].isMinIncluded() && getAmount(val).equals(getAmount(criteria.getRanges()[i].getMin())))))
    				&& (getAmount(val).isLessThan(getAmount(criteria.getRanges()[i].getMax()))
    				|| (criteria.getRanges()[i].isMaxIncluded() && getAmount(val).equals(getAmount(criteria.getRanges()[i].getMax()))))) {
    			return new TimestampedTextAdapter(criteria.getLabels()[i], ((TimestampedMeasureAdapter)val).getTimestamp());
    		}
    	}
//    	System.out.println(val.getValue() + "(" + (criteria.getLabels().length-1) 
//    			+ ") -> " + criteria.getLabels()[criteria.getLabels().length-1]);
    	return new TimestampedTextAdapter("OUT OF RANGE", ((TimestampedMeasureAdapter)val).getTimestamp());
    }


    private Criteria[] parseTransformationCriteria(String ranges, String labels) {
    	return null;
    }

    private class Criteria {

    	private Range[] ranges;
    	private String[] labels;
    	
    	public Criteria(String ranges, String labels) throws PluginException{
			this.ranges = parse(ranges);
			this.labels = labels.split(","); 
		}
    	
    	public Range[] getRanges() {
			return ranges;
		}

		public String[] getLabels() {
			return labels;
		}

		/**
		 * @param s
		 * @return
		 * @throws PluginException
		 */
		private Range[] parse(String s) throws PluginException{
			String[] rangesParam = s.split(";");
    		String[] rangeValues = null;
    		MeasureAdapter[][] range = new MeasureAdapter[rangesParam.length][2];
    		ranges = new Range[rangesParam.length];
	     	for(int i=0; i<range.length; i++){
	     		ranges[i] = new Range();
    			rangeValues = rangesParam[i].split(",");
   				rangeValues[0] = rangeValues[0].trim();
   				rangeValues[1] = rangeValues[1].trim();
    			char left = rangeValues[0].charAt(0); 
    			rangeValues[0] = rangeValues[0].substring(1).trim();
//    			System.out.println("'"+rangeValues[0]+"'");
    			char right = rangeValues[1].charAt(rangeValues[1].length()-1); 
    			rangeValues[1] = rangeValues[1].substring(0, rangeValues[1].length()-1).trim();
//    			System.out.println("'"+rangeValues[1]+"'");
    			if(left == '[')
    				ranges[i].minIncluded=true;
    			else if(left == ']')
    				ranges[i].minIncluded=false;
    			else
    				throw new PluginException(PluginExceptionsEnum.IllegalParameter, this.getClass().getName(), "The format of a ranges is [a,b] or ]a,b] or [a,b[!");
   				if(right == ']')
       				ranges[i].maxIncluded=true;
   				else if (right == '[')
   				  	ranges[i].maxIncluded=false;
    			else
    				throw new PluginException(PluginExceptionsEnum.IllegalParameter, this.getClass().getName(), "The format of a ranges is [a,b] or ]a,b] or [a,b[!");
//   				System.out.println(this.getClass().getName() + " START setting ranges");
   				ranges[i].min=new MeasureAdapter(Measure.valueOf(Double.parseDouble(rangeValues[0].trim()), _unitOfMeasure));
   				ranges[i].max=new MeasureAdapter(Measure.valueOf(Double.parseDouble(rangeValues[1].trim()), _unitOfMeasure));
//  				System.out.println(this.getClass().getName() + "END of setting ranges");
	     	
	     	}
    		return ranges;
    	}
//		private double[][] checkRangesFormat(String rangesParameter){
//    		String[] ranges = rangesParameter.split(";");
//    		String[] rangeValues;
//    		double[][] range = new double[ranges.length][2];
//        	for(int i=0; i<range.length; i++){
//    			rangeValues = ranges[i].split(",");
//    			range[i][0] = Double.parseDouble(rangeValues[0]);
//    			range[i][1] = Double.parseDouble(rangeValues[1]);
//    		}
//    		return range;
//    	}
//    	private void checkLabelsFormat(){
//
//    	}
        public String toString(){
        	String s = labels[0];
        	for(int i=1; i<labels.length;i++)
        		s +=  "," + labels[i];
        	s += "\n";
        	for(int i=0; i<ranges.length;i++)
        		s += " ["+ranges[i].min+","+ranges[i].max +"] ";
        	return s;
        }
    }
    
    private TimestampedTextAdapter getTimestampedTextualData(String value, Date time) throws PluginException {
		try {
			return new TimestampedTextAdapter(value, time);
		} catch (NumberFormatException e) {
			this.error(this, this.getPluginId(), this.getId(), "Value '" + value + " is not valid");
			throw new PluginException(PluginExceptionsEnum.InputTypeNotRecognized, this.getPluginId(), "Value '" + value + " is not valid");
		}    	
    }
    
    private class Range{
    	private MeasureAdapter min; 
    	private MeasureAdapter max;
    	private boolean minIncluded = false; 
    	private boolean maxIncluded = false; 
    	
    	public MeasureAdapter getMax() {
  			return max; //new MeasureAdapter(Measure.valueOf(max.getMeasure().doubleValue(Unit.ONE), _unitOfMeasure));
		}


		public boolean isMaxIncluded() {
			return maxIncluded;
		}

		public MeasureAdapter getMin() {
			return min; //new MeasureAdapter(Measure.valueOf(min.getMeasure().doubleValue(Unit.ONE), _unitOfMeasure));
		}

		public boolean isMinIncluded() {
			return minIncluded;
		}

		public String toString(){
    		return ((minIncluded)?"[":"]")+min+","+max+((maxIncluded)?"]":"[");
    	}
    }
    
	private Amount getAmount(MeasureAdapter ma) {
		return Amount.valueOf(ma.getMeasure().doubleValue(_unitOfMeasure), _unitOfMeasure);
	}
    
}
